/**
 * Copyright 2010 Mark Wyszomierski
 */
package com.joelapenna.foursquared.util;

import android.net.Uri;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Message;
import android.os.Process;
import android.util.Log;

import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Observable;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;


/**
 * Acts as a per-activity image download manager. This is for testing only.
 * The global resource fetcher's requests live between activities. In most
 * cases we want to stop all requests as soon as an activity is completed.
 * 
 * Create an instance of this fetcher as part of an activity's state holder.
 * Add request urls to it as usual. When the activity is finishing, just
 * call shutdown();
 * 
 * We are limited to a single download thread, and we set its process
 * priority to the lowest possible. Therefore image downloads won't 
 * consume much cpu while the user is interacting with the UI (which will
 * mostly end up being the user scrolling lists).
 * 
 * @date October 12, 2010
 * @author Mark Wyszomierski (markww@gmail.com)
 */
public class RemoteResourceFetcher extends Observable {
    
    private static final String TAG = "RemoteResourceFetcher-NEW";
    private static final int MSG = 500;
    
    private Object mGuard;
    private Set<String> mRequests;
    private AtomicBoolean mRun;
    private WorkerThread mThread;
    private DiskCache mDiskCache;
    
    
    public RemoteResourceFetcher(String cacheName) {
        this(new BaseDiskCache("foursquare", cacheName));
    }
    
    public RemoteResourceFetcher(DiskCache cache) {
        mGuard = new Object();
        mDiskCache = cache;
        mRequests = new LinkedHashSet<String>();
        mRun = new AtomicBoolean(true);
        mThread = new WorkerThread();
        mThread.start();
    }

    public void fetch(Uri uri, String hash) {
        String url = uri.toString();
        synchronized (mGuard) {
            if (!mRequests.contains(url)) {
                mRequests.add(url);
                mGuard.notify();
            }
        }
    }
    
    public void shutdown() {
        mRun.set(false);
        synchronized (mGuard) {
            mGuard.notify();
        }
        mHandler.removeMessages(MSG);
    }
    
    /**
     * Probably makes sense to call this whenever an activity is finishing, we probably
     * don't care about any of those requests, even if some may be useful in the next
     * activity to be shown.
     */
    public void clearRequests() {
        synchronized (mGuard) {
            mRequests.clear();
        }
    }
    
    private class WorkerThread extends Thread {
        public WorkerThread() {
            Log.e(TAG, "Worker thread started.");
        }
        
        @Override
        public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
            
            while (mRun.get()) {
                List<String> requests = new ArrayList<String>();
                synchronized (mGuard) {
                    if (mRequests.size() > 0) {
                        // Copy over values, get out of guard.
                        for (String it : mRequests) {
                            requests.add(it);
                        }
                        
                        // Clear out current requests while we're guarded.
                        mRequests.clear();
                    } else {
                        Log.e(TAG, "Worker thread waiting for more jobs please...");
                        try {
                            mGuard.wait();
                        } catch (InterruptedException ex) {
                        }
                    }
                }
                
                // Service any requests we had, do them in reverse order.
                for (int i = requests.size()-1; i > -1; i--) {
                    // Store is being done in the context of this background thread,
                    // so make sure that mDiskCache.store() is thread-safe.
                    String url = requests.get(i);
                    mDiskCache.store(Uri.encode(url), downloadImage(url));
                    mHandler.sendMessage(mHandler.obtainMessage(MSG, url));
                }
            }
        }
    };
    
    private InputStream downloadImage(String url) {
        try {
            URLConnection conn = new URL(url).openConnection();
            conn.setConnectTimeout(7 * 1000);
            conn.setReadTimeout(7 * 1000);
            conn.connect(); 
            return conn.getInputStream(); 
       } catch (Exception ex) { 
           Log.e(TAG, "Error getting bitmap at url [" + url + "].", ex); 
       }
      
       return null;       
    }
    
    private Handler mHandler = new Handler(new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            notifyObservers(msg.obj);
            return true;
        }
    });
    
    @Override
    public void notifyObservers(Object data) {
        setChanged();
        super.notifyObservers(data);
    }
}